using System;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using Microsoft.VisualBasic;

namespace Baula.TextGen.ActiveTemplating
{
    public interface IExecutor
    {
        void Execute(string code);
        string Output { get; }
        IContext Context { get; }
    }

    public class Executor : IExecutor
    {
        private const string FixtureTemplate = @"
Imports Baula.TextGen.ActiveTemplating
Imports System.Linq
Imports System.Text

Public Class ExecutionFixture
    Private Shared _OutputBuilder As StringBuilder

    Public Shared Sub Execute(ByVal Context As Context, ByVal OutputBuilder As StringBuilder, ByVal Listener As ExecutionListener)
        _OutputBuilder = OutputBuilder
        Dim Forms As IFormsContext = Context.Forms
        Dim Tables As ITablesContext = Context.Tables
{0}
    End Sub

    Private Shared Sub AppendLine()
        _OutputBuilder.AppendLine()
    End Sub

    Private Shared Sub AppendLine(ByVal Text As String)
        _OutputBuilder.AppendLine(Text)
    End Sub

    Private Shared Sub Append(ByVal Text As String)
        _OutputBuilder.Append(Text)
    End Sub
End Class
";
        private const string ExecuteMethodName = "Execute";
        private const string ExecutionFixtureName = "ExecutionFixture";
        private readonly IContext _context;
        private StringBuilder _outputBuilder;

        public Executor(IContext context)
        {
            _context = context;
        }

        public string Output
        {
            get { return _outputBuilder.ToString(); }
        }

        public IContext Context
        {
            get { return _context; }
        }

        public ExecutionListener Listener { get; set; }

        public void Execute(string code)
        {
            var vbCodeProvider = new VBCodeProvider();
            CodeDomProvider codeDomProvider = vbCodeProvider;
            var options = new CompilerParameters
                          {
                              GenerateInMemory = true,
                              CompilerOptions = "/optioninfer+"
                          };
            
//#if DEBUG
//            options.GenerateInMemory = false;
//            options.GenerateExecutable = false;
//            options.IncludeDebugInformation = true;
//            options.TempFiles = new TempFileCollection(Environment.GetEnvironmentVariable("TEMP"), true);
//            options.TempFiles.KeepFiles = true;
//#else
//            options.GenerateInMemory = true;
//#endif
            options.ReferencedAssemblies.Add("System.Core.dll");
            options.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);

            var fixtureCode = string.Format(FixtureTemplate, code);
            Debug.WriteLine("Fixture Code:\r\n{0}", (object)fixtureCode);
            var results = codeDomProvider.CompileAssemblyFromSource(options, fixtureCode);
            if (results.Errors.Count > 0)
            {
                var compilerError = results.Errors[0];
                throw new Exception(string.Format("{0} (Line {1}, Column {2})", compilerError.ErrorText, compilerError.Line, compilerError.Column));
            }

            var debugInfo = ExecuteFixture(results.CompiledAssembly);
            OutputDebugInfo(debugInfo);
        }

        private object ExecuteFixture(Assembly assembly)
        {
            var fixture = assembly.GetType(ExecutionFixtureName);
            if (fixture == null)
                throw new Exception(string.Format("Type {0} not found.", ExecutionFixtureName));
            var executeMethod = fixture.GetMethod(ExecuteMethodName, BindingFlags.Static | BindingFlags.Public);
            if (executeMethod == null)
                throw new Exception(string.Format("Method {0} not found.", ExecuteMethodName));
            _outputBuilder = new StringBuilder();
            
            executeMethod.Invoke(null, new object[] { _context, _outputBuilder, Listener });

            return Listener != null
                       ? Listener.DebugInfo
                       : null;
        }

        private void OutputDebugInfo(object debugInfo)
        {
            if (debugInfo != null)
            {
                var debugInfoAsString = debugInfo as string;
                if (debugInfoAsString != null)
                {
                    Debug.WriteLine("Debug info:\r\n{0}", debugInfoAsString);
                }
                var debugInfoAsEnumerable = debugInfo as IEnumerable;
                if (debugInfoAsEnumerable != null)
                {
                    var asList = new List<object>((IEnumerable<object>)debugInfoAsEnumerable);
                    Debug.WriteLine("Debug info:");
                    foreach (var item in debugInfoAsEnumerable)
                    {
                        Debug.WriteLine("\t{0}: {1}", item.GetType().FullName, item);
                    }
                    Debug.WriteLine("{0} item(s)", asList.Count);
                }
            }
        }
    }
}